package jssia.database.mysql.transactions;

import java.sql.Connection;
import java.sql.SQLException;

import jssia.exceptions.DatabaseConnectionException;

import com.mysql.jdbc.exceptions.jdbc4.CommunicationsException;
import com.mysql.jdbc.exceptions.jdbc4.MySQLNonTransientConnectionException;
import com.mysql.jdbc.exceptions.jdbc4.MySQLTransactionRollbackException;
import com.smoed.common.logging.Log;
import com.smoed.common.utilities.Sleeper;

public final class TransactionManager {
    private TransactionManager() {
    }

    private static final int DEADLOCK_RETRY_TIMEOUT_MILLISECONDS = 35;

    public static <T> T executeTransaction(Connection conn, Transaction<T> t) {
        T dbResult;

        while (true) {
            try {
                try {
                    dbResult = t.execute();
                    break;
                } finally {
                    conn.setAutoCommit(true);
                }
            } catch (CommunicationsException e) {
                printSQLException(e);
                throw new DatabaseConnectionException(e.getMessage());
            } catch (MySQLNonTransientConnectionException e) {
                printSQLException(e);
                throw new DatabaseConnectionException(e.getMessage());
            } catch (MySQLTransactionRollbackException e) {
                printSQLException(e);
                Sleeper.sleepMillis(DEADLOCK_RETRY_TIMEOUT_MILLISECONDS);
            } catch (SQLException e) {
                printSQLException(e);
                // TODO we need to handle this situation,
                // proabably the best thing to do for now
                // is to shutdown the entire system until
                // we fix the problem.
            }
        }

        return dbResult;
    }

    public static void executeTransaction(Connection conn, VoidTransaction t) {
        while (true) {
            try {
                try {
                    t.execute();
                    return;
                } finally {
                    conn.setAutoCommit(true);
                }
            } catch (CommunicationsException e) {
                printSQLException(e);
                throw new DatabaseConnectionException(e.getMessage());
            } catch (MySQLNonTransientConnectionException e) {
                printSQLException(e);
                throw new DatabaseConnectionException(e.getMessage());
            } catch (MySQLTransactionRollbackException e) {
                printSQLException(e);
                Sleeper.sleepMillis(DEADLOCK_RETRY_TIMEOUT_MILLISECONDS);
            } catch (SQLException e) {
                printSQLException(e);
                System.exit(-1);
            }
        }
    }

    private static final String SQL_EXCEPTION_LOG_TITLE = "\n\n--- SQLException caught ---\n";
    private static final String SQL_EXCEPTION_LOG_MESSAGE_LABEL = "Message:   ";
    private static final String SQL_EXCEPTION_LOG_SQL_STATE_LABEL = "\nSQLState:  ";
    private static final String SQL_EXCEPTION_LOG_ERROR_CODE_LABEL = "\nErrorCode: ";

    public static void printSQLException(SQLException e) {
        final StringBuilder sqlExceptionLogMessage = new StringBuilder(SQL_EXCEPTION_LOG_TITLE);
        while (e != null) {
            sqlExceptionLogMessage.append(SQL_EXCEPTION_LOG_MESSAGE_LABEL);
            sqlExceptionLogMessage.append(e.getMessage());
            sqlExceptionLogMessage.append(SQL_EXCEPTION_LOG_SQL_STATE_LABEL);
            sqlExceptionLogMessage.append(e.getSQLState());
            sqlExceptionLogMessage.append(SQL_EXCEPTION_LOG_ERROR_CODE_LABEL);
            sqlExceptionLogMessage.append(e.getErrorCode());
            e = e.getNextException();
        }
        Log.error(sqlExceptionLogMessage.toString(), 5);
    }
}
